
#include <stdio.h>
#include <stdlib.h>
#include "../iface/ZlibInterface.h"
#include "GZDecompress.h"

/** Construct the class with no bucket brigades. This means that this
 * is to be used for block and not pipe decompression. */
GZDecompress::GZDecompress()
{
   input = NULL;
   output = NULL;
   strm = NULL;
}

/** Construct the class with bucket brigades. This means that this can
 * be used for both block and pipe decompression.
 * 
 * @param iinput The bucket brigade for input.
 * 
 * @param ioutput The bucket brigade for output. */
GZDecompress::GZDecompress(APRBucketBrigade &iinput, APRBucketBrigade &ioutput)
{
   input = &iinput;
   output = &ioutput;
   strm = NULL;
}

/** Destructor for the zlib decompression class. */
GZDecompress::~GZDecompress()
{
}

/** Decompress data block and return decompressed data. The
 * decompressed data returned must be freed by the caller.
 * 
 * @param inputblock A block of plaintext data to decompress
 * (decompressing encrypted data doesn't really work).
 * 
 * @param inputsize The length of the plaintext data block to
 * decompress.
 * 
 * @param[out] outputsize The length of the decompressed block of data
 * returned. The data returned must be freed by the caller. */
char *GZDecompress::processDataBlock(const char *inputblock, apr_size_t inputsize, apr_size_t *outputsize)
{
   apr_size_t max_output_size = inputsize;
   uLongf dest_length = 0;
   char *result = NULL;
   int zstatus = Z_BUF_ERROR;
   // using the theorectical limit of Zlib decompression
   while ((zstatus == Z_BUF_ERROR) && (max_output_size < (1031*inputsize)))
   {
      if (result) free(result);
      result = (char *)malloc(sizeof(char)*max_output_size);
      dest_length = max_output_size;
      zstatus = uncompress((Bytef *)result, &dest_length, (const Bytef *)inputblock, inputsize);
      max_output_size *= 2;
   }
   CheckZlibError(zstatus);
   if (zstatus != Z_OK)
      errprint("if this is a Z_BUF_ERROR, you're in trouble");
   if (dest_length > 0)
   {
      if (outputsize)
         *outputsize = dest_length;
      return result;
   }
   free(result);
   return NULL;
}

/** Begin the process of decompressing a pipe of data.
 * 
 * @return Zero on success, non-zero on failure. */
int GZDecompress::processPipeBegin()
{
   if (strm)
   {
      errprint("already in use");
      return 1;
   }
   int zstatus;
   strm = (z_stream *)malloc(sizeof(z_stream));

   strm->zalloc = Z_NULL;
   strm->zfree = Z_NULL;
   strm->opaque = Z_NULL;
   
   zstatus = inflateInit(strm);
   CheckZlibError(zstatus);
   if (zstatus != Z_OK) return zstatus;
   return 0;
}

/** Process any outstanding data on the input pipe, and place any
 * processed decompressed data into the output pipe.
 * 
 * @return Zero on success, non-zero on failure. */
int GZDecompress::processPipeStep()
{
   unsigned char *itemp_block = NULL;
   unsigned char *otemp_block = NULL;
   apr_size_t input_block = input->realLength();
   apr_size_t readbytes = 0;
   apr_size_t writebytes = 0;
   apr_size_t wrotebytes = 0;
   int retval = 1;
   int zstatus = Z_OK;
   while (input_block != 0)
   {
      itemp_block = (unsigned char *)malloc(sizeof(unsigned char)*input_block);
      otemp_block = (unsigned char *)malloc(sizeof(unsigned char)*input_block);
      readbytes = input->readFromStream((char *)itemp_block, input_block);
      if (readbytes != input_block)
      {
         errprint("failed to read data from plain bucket brigade (read %" APR_SIZE_T_FMT ", had %" APR_SIZE_T_FMT "), buffer unfinished", readbytes, input_block);
         goto stopsteps;
      }
   
      strm->avail_in = input_block;
      strm->next_in = itemp_block;
      strm->avail_out = input_block;
      strm->next_out = otemp_block;
      zstatus = inflate(strm, Z_NO_FLUSH);
      CheckZlibError(zstatus);
      writebytes = input_block - (strm->avail_out);
      wrotebytes = output->writeToStream((const char *)otemp_block, writebytes);
      if (wrotebytes != writebytes)
      {
         errprint("failed to write data to decompressed bucket brigade (wrote %" APR_SIZE_T_FMT ", had %" APR_SIZE_T_FMT "), likely corrupt", wrotebytes, writebytes);
         goto stopsteps;
      }
   
      input->removeFromStream(input_block - (strm->avail_in));
      if (itemp_block) free(itemp_block);
      itemp_block = NULL;
      if (otemp_block) free(otemp_block);
      otemp_block = NULL;
      input_block = input->realLength();
   }
   retval = 0;
stopsteps:
   if (itemp_block) free(itemp_block);
   itemp_block = NULL;
   if (otemp_block) free(otemp_block);
   otemp_block = NULL;
   return retval;
}

/** Process the pipe step and record the movement of data to determine
 * when the process is complete.
 * 
 * @param[out] movementin Records data input in bytes.
 * 
 * @param[out] movementout Records data output in bytes. */
int GZDecompress::processPipeStep(int *movementin, int *movementout)
{
   apr_size_t before_input = input->realLength();
   apr_size_t before_output = output->realLength();
   int retval = processPipeStep();
   apr_size_t after_input = input->realLength();
   apr_size_t after_output = output->realLength();
   if (movementin)
      *movementin = before_input - after_input;
   if (movementout)
      *movementout = after_output - before_output;
   return retval;
}

/** Process any outstanding plaintext data in the pipe and finalise
 * the decompression process.
 * 
 * @return Zero on success, non-zero on failure. */
int GZDecompress::processPipeEnd()
{
   processPipeStep();
   const int blocksize = 0x10000;
   unsigned char *otemp_block = (unsigned char *)malloc(sizeof(unsigned char)*blocksize);
   do
   {
      strm->avail_in = 0;
      strm->next_in = NULL;
      strm->avail_out = blocksize;
      strm->next_out = otemp_block;
      int zstatus = inflate(strm, Z_FINISH);
      CheckZlibError(zstatus);
      apr_size_t writebytes = blocksize - (strm->avail_out);
      apr_size_t wrotebytes = output->writeToStream((const char *)otemp_block, writebytes);
      if (wrotebytes != writebytes)
      {
         errprint("failed to write data to decompressed bucket brigade (wrote %" APR_SIZE_T_FMT ", had %" APR_SIZE_T_FMT "), likely corrupt", wrotebytes, writebytes);
         inflateEnd(strm);
         free(strm);
         strm = NULL;
         free(otemp_block);
         return 1;
      }
   } while (strm->avail_out == 0);
   inflateEnd(strm);
   free(strm);
   strm = NULL;
   free(otemp_block);
}
